import java.util.*;
import java.math.*;

public class DoubleShareRandom{

    Player p;
    int playerID;
    SharingProtocols sharingProtocol;
    HIM M;
    int d1, d2;

    HashMap<Integer, BigInteger[]> doubleShares; //Holds the [s_i] values from step 1 in
                                                 //DoubleShareRandom
    HashMap<Integer, BigInteger[]> doubleShareStep3; //Holds the received values of [s_i] from 
                                                     //step 3 in DoubleShareRandom
    HashMap<Integer, BigInteger[]> randomDoubleShares; //Holds [r_0]_(d,d')...[r_T]_(d,d') values
    HashMap<Integer, BigInteger[]> res;
    HashMap<Integer, Integer> doubleShareWaitForCheck;

    public DoubleShareRandom(Player p, SharingProtocols sp, HIM M){
        this.p = p;
        playerID = p.getID();
        sharingProtocol = sp;
        this.M = M;
        doubleShares = new HashMap<Integer, BigInteger[]>();
        doubleShareStep3 = new HashMap<Integer, BigInteger[]>();
        randomDoubleShares = new HashMap<Integer, BigInteger[]>();
        res = new HashMap<Integer, BigInteger[]>();     
        doubleShareWaitForCheck = new HashMap<Integer, Integer>();
    }

    /**
     * Part of, and start of: DoubleShareRandom
     */
    public void doubleShareRandom(int degree1, int degree2, HashMap<Integer, MessageList> msgs){
        Utilities.lock.lock();

        if(msgs != null){
            //check mode
            int me = p.impersonate;
            ArrayList<BigInteger[][]> rand = msgs.get(me).getRandomness();
            BigInteger[][] sending = rand.remove(0);
            for(Integer i : msgs.keySet()){                
                MessageList ml = msgs.get(i);
                int c1 = ml.getMessageCounter(me);
                BigInteger match1 = (BigInteger)ml.getNextMsg(me);
                int c2 = ml.getMessageCounter(me);
                BigInteger match2 = (BigInteger)ml.getNextMsg(me);
                if(!match1.equals(sending[0][i])){
                    //One is corrupted
                    p.faultDetected(me, i, sending[0][i], match1, c1);
                    Utilities.lock.unlock();
                    return;
                }
                else if(!match2.equals(sending[1][i])){
                    //one is corrupted
                    p.faultDetected(me, i, sending[1][i], match2, c2);
                    Utilities.lock.unlock();
                    return;
                }
                else{
                    //all is good - continue
                    if(i == me)
                        receiveDoubleShare(match1, match2, i, msgs);
                    else{
                        BigInteger one = (BigInteger)msgs.get(me).getNextMsg(i);
                        BigInteger two = (BigInteger)msgs.get(me).getNextMsg(i);
                        receiveDoubleShare(one, two, i, msgs);
                    }
                }
            }
        }
        else{
            d1 = degree1; d2 = degree2;
            //first choose random secret s to share
            sharingProtocol.generateSecret();
            //Then doubleShareRandom to get [s]_{d1,d2}
            BigInteger[][] doubleShare = sharingProtocol.doubleShareRandom(degree1, degree2);

            //Add The choices of randomness to the messageList.
            p.addMessageRandomness(doubleShare);
            
            //Now distribute [s_i]_{d1,d2} to player P_i:
            for(Integer i : p.players.keySet()){            
                ArrayList<Object> list = new ArrayList<Object>();
                list.add(doubleShare[0][i]);
                list.add(doubleShare[1][i]);
                p.sendMessage(Utilities.doubleShareRandom, list, i);                        
            }
        }
        Utilities.lock.unlock();
    }

    /**
     * Part of: DoubleShareRandom. I checked at doubleShareRandom() if
     * the randomness was correctly chosen, so I do not need to check
     * again.
     */
    public void receiveDoubleShare(BigInteger share1, BigInteger share2, int senderID, HashMap<Integer, MessageList> msgs){
        if(doubleShares.containsKey(senderID)){
            //ID already there.. Someone might try to trick us.. 
            p.print("Error in ReceiveDoubleShare");
            p.print("Doubleshares contains the following: ");
            for(Integer i : doubleShares.keySet()){
                p.print("d-share no. "+ i+"  - First: " + doubleShares.get(i)[0] + ", second: " + doubleShares.get(i)[1]);
            }
            p.print("And the one who tried to send something again was:");
            p.print("Player " + senderID);
            p.error(p.DOUBLESHARERANDOM);
            return;
        }

        BigInteger[] sharings = new BigInteger[2];
        sharings[0] = share1;
        sharings[1] = share2;
        doubleShares.put(senderID, sharings);
        if(doubleShares.size() == Utilities.n_prime){
            HashMap<Integer, BigInteger[]> doubleSharesCopy = new HashMap<Integer, BigInteger[]>();
            doubleSharesCopy.putAll(doubleShares);
            doubleShares.clear();

            //Step 2: Apply M to get random [r_i]_{d,d'}            
            BigInteger[] tmp1 = new BigInteger[Utilities.n_prime];
            BigInteger[] tmp2 = new BigInteger[Utilities.n_prime];

            int index = 0;
            for(Integer i : doubleSharesCopy.keySet()){
                sharings = doubleSharesCopy.get(i);
                tmp1[index] = sharings[0];
                tmp2[index] = sharings[1];
                index++;
            }           

            BigInteger[] r_1 = M.applyShares(tmp1);
            BigInteger[] r_2 = M.applyShares(tmp2);
            index = 0;
            for(Integer i : doubleSharesCopy.keySet()){
                BigInteger[] results = new BigInteger[2];
                results[0] = r_1[index];
                results[1] = r_2[index];

                randomDoubleShares.put(i, results);
                index++;                
            }

            //Prepare the first T doubleShares no matter if the check
            //succeeded or not. Will be checked later on.
            int count = 1;
            for(Integer i : randomDoubleShares.keySet()){
                if(count > Utilities.T){
                    break;
                }
                res.put(i,randomDoubleShares.get(i));
                count++;
            }

            
            //Step 3, part 1: CHECK for i = T+1,...,n': P_j \in P' sends his
            //doubleshare of [s_i] to P_i

            ArrayList<Object> list = new ArrayList<Object>();
            HashMap<Integer, Integer> checkMap = new HashMap<Integer, Integer>();
            count = 1;
            for(Integer i : p.players.keySet()){
                if(count > Utilities.T){
                    sharings = doubleSharesCopy.get(i);
                    try{
                        list.add(sharings[0]);
                        list.add(sharings[1]);                
                    }
                    catch(NullPointerException e){
                        e.printStackTrace();
                        p.print("");
                        p.print("What's inside doubleSharesCopy:");
                        for(Integer j : doubleSharesCopy.keySet()){
                            p.print_less("("+j+", "+doubleSharesCopy.get(j)+"), ");
                        }
                        System.exit(-1);
                    }
                    if(msgs == null)
                        p.sendMessage(Utilities.receiveCheckDoubleShare, list, i);
                    else{
                        //check mode TODO: calculate what should have
                        //been sent, and check up against what the
                        //players says they received. - if check mode,
                        //then it is called with no option for
                        //corrupted shares. So we can use those as ground truth.
                        int me = p.impersonate;
                        MessageList ml = msgs.get(i);
                        int c1 = ml.getMessageCounter(me);
                        BigInteger s1 = (BigInteger)ml.getNextMsg(me);
                        int c2 = ml.getMessageCounter(me);
                        BigInteger s2 = (BigInteger)ml.getNextMsg(me);
                        if(!s1.equals(sharings[0])){
                            p.faultDetected(me, i, sharings[0], s1, c1);
                            return;
                        }
                        else if(!s2.equals(sharings[1])){
                            p.faultDetected(me, i, sharings[1], s2, c2);
                            return;
                        }
                        else{
                            //all is good - continue
                            if(me == i){
                                //I am one of those who has to verify
                                //dsr check - however, since we just
                                //checked the sent stuff to there, we
                                //just continue
                            }
                            else{
                                //Maybe correct, since we need to skip some things.
                                msgs.get(me).getNextMsg(i);
                                msgs.get(me).getNextMsg(i);
                            }
                        }
                    }
                    list.clear();
                    checkMap.put(i,i);
                }
                count++;
            }
            
            for(Integer i : p.players.keySet()){
                if(msgs == null){
                    if(!checkMap.containsKey(playerID)){
                        //This is players who does NOT check stuff. They
                        //are now told to wait until checkers are done checking.
                        p.sendMessage(Utilities.DOUBLESHARE_WAITFORCHECK, list, i);
                    }
                }                
                else{ //check mode
                    if(p.impersonate == i){
                        //let's continue no matter if I am verifier or not:
                        for(Integer j : p.players.keySet()){
                            doubleShare_waitForCheck(j);
                        }
                    }
                }
            }            
        }
    }
    
    /**
     * Part of: DoubleShareRandom
     * Makes sure if the shares passes the checks: s1 of degree d1 has
     * to lie on a polynomial g(.) of degree d1 and vice versa with
     * s2 [ g'(.) ]. Also checks if g(0) = g'(0).
     */
    public void receiveCheckDoubleShare(BigInteger s1, BigInteger s2, int sender){
        Utilities.lock.lock();
        if(doubleShareStep3.containsKey(sender)){
            //Someone tried to send a value twice. Not accepted.
            p.print("Error in receiveCheckDoubleShare");
            p.error(p.DOUBLESHARERANDOM);
            //for now, just exit the method.
            Utilities.lock.unlock();
            return;
        }
        //first make sure that he got all n' shares - do not begin
        //check before that is secured
        BigInteger[] sharings = new BigInteger[2];
        sharings[0] = s1;
        sharings[1] = s2;
        doubleShareStep3.put(sender, sharings); 
        
        if(doubleShareStep3.size() == Utilities.n_prime){
            //All n' sharings received.
            //Step 3 - part 2: Check consistency for the shares. 
            if(!checkConsistency() ){
                //unhappy - notify something that something is not right.
                p.setHappy(false);                
            }
            else{
                //We are happy - passed the check. 
            }
            ArrayList<Object> list = new ArrayList<Object>();
            for(Integer i : p.players.keySet()){
                p.sendMessage(Utilities.DOUBLESHARE_WAITFORCHECK, list, i);
            }

            //reset our own shares since we are now done with this protocol:
            doubleShareStep3.clear();
        }
        Utilities.lock.unlock();
    }

    private boolean checkConsistency(){
        HashMap<Integer, BigInteger> shares1 = new HashMap<Integer, BigInteger>();
        HashMap<Integer, BigInteger> shares2 = new HashMap<Integer, BigInteger>();
        for(Integer i : doubleShareStep3.keySet()){
            shares1.put(i, doubleShareStep3.get(i)[0]);
            shares2.put(i, doubleShareStep3.get(i)[1]);
        }

        for(Integer i : shares1.keySet()){            
            if(!shares1.get(i).equals(sharingProtocol.lagrange(shares1, d1, i))){
                return false;
            }
        }
        for(Integer i : shares2.keySet()){            
            if(!shares2.get(i).equals(sharingProtocol.lagrange(shares2, d2, i))){
                return false;
            }
        }
        BigInteger s1 = sharingProtocol.lagrange(shares1, d1, 0);
        BigInteger s2 = sharingProtocol.lagrange(shares2, d2, 0);
        
        if(!s1.equals(s2))
            return false;
        return true;
    }

    public void doubleShare_waitForCheck(int sender){
        Utilities.lock.lock();
        if(doubleShareWaitForCheck.containsKey(sender)){
            print("GOT ERROR IN WAITFORCHECK - This player ID send someting twice: " + playerID);
            Utilities.lock.unlock();
            p.error(p.DOUBLESHARERANDOM);
            return;
        }
        doubleShareWaitForCheck.put(sender, sender);

        if(doubleShareWaitForCheck.size() == Utilities.n_prime){
            doubleShareWaitForCheck.clear(); //reset
            p.setRandomDoubleShares(res);
            res = new HashMap<Integer, BigInteger[]>(); //reset
        }
        Utilities.lock.unlock();
    }

    public void print(String msg){
        if(Utilities.DEBUG){
            System.out.println(msg);
        }
    }
    
    public void print_less(String msg){
        if(Utilities.DEBUG){
            System.out.print(msg);
        }
    }


}
